﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using Tesla.Content;
using Tesla.Core;
using Tesla.Math;

namespace Tesla.Bounding {
    
    /// <summary>
    /// BoundingVolume is an abstract class that represents a 3D shape that encapsulates some volume, which are used
    /// to represent a more complex 3D mesh. All volumes inherit from this base class, and contain a variety of
    /// helpful methods for checking intersections.
    /// </summary>
    public abstract class BoundingVolume : ISavable {
        /// <summary>
        /// Center of the bounding volume, this is common to all bounding volumes.
        /// </summary>
        protected Vector3 m_center;

        /// <summary>
        /// Gets or sets the center of the bounding volume.
        /// </summary>
        public Vector3 Center {
            get {
                return m_center;
            }
            set {
                m_center = value;
            }
        }

        /// <summary>
        /// Gets the volume of the bounding volume.
        /// </summary>
        public abstract float Volume {
            get;
        }

        /// <summary>
        /// Gets the bounding type.
        /// </summary>
        public abstract BoundingType BoundingType {
            get;
        }

        /// <summary>
        /// Default constructor, bounding volume will have its center at the origin.
        /// </summary>
        public BoundingVolume() {
            m_center = Vector3.Zero;
        }

        /// <summary>
        /// Instantiate a new bounding volume with the specified center.
        /// </summary>
        /// <param name="center"></param>
        public BoundingVolume(Vector3 center) {
            m_center = center;
        }

        /// <summary>
        /// Sets the center of the volume from the specified coordinates.
        /// </summary>
        /// <param name="x">X coordinate</param>
        /// <param name="y">Y coordinate</param>
        /// <param name="z">Z coordinate</param>
        public void SetCenter(float x, float y, float z) {
            m_center.X = x;
            m_center.Y = y;
            m_center.Z = z;
        }

        /// <summary>
        /// Sets this bounding volume to contain the specified volume or to copy from.
        /// </summary>
        /// <param name="volume">Volume to copy from</param>
        public abstract void Set(BoundingVolume volume);

        /// <summary>
        /// Returns a new instance of this bounding volume.
        /// </summary>
        /// <returns>Copied bounding volume</returns>
        public abstract BoundingVolume Clone();


        /// <summary>
        /// Computes the distance from the center of this volume
        /// to the point.
        /// </summary>
        /// <param name="point">Vector3</param>
        /// <returns>Distance</returns>
        public float DistanceTo(Vector3 point) {
            return Vector3.Distance(Center, point);
        }

        /// <summary>
        /// Compute the distance to the nearest edge of the volume
        /// from the point
        /// </summary>
        /// <param name="point">Vector3</param>
        /// <returns>Distance from neatest edge</returns>
        public abstract float DistanceToEdge(Vector3 point);


        /// <summary>
        /// Computes this bounding volume from a set of 3D points
        /// </summary>
        /// <param name="points">Array of Vectors</param>
        public abstract void ComputeFromPoints(Vector3[] points);

        /// <summary>
        /// Compute this bounding volume from a set of 3D points.
        /// </summary>
        /// <param name="points">DataBuffer of points</param>
        public void ComputeFromPoints(DataBuffer<Vector3> points) {
            ComputeFromPoints(points.Buffer);
        }

        /// <summary>
        /// Compute this bounding volume from a set of primitives.
        /// </summary>
        /// <param name="vertices">Vertex buffer</param>
        /// <param name="indices">Index buffer</param>
        public abstract void ComputeFromPrimitives(Vector3[] vertices, int[] indices);

        /// <summary>
        /// Compute this bounding volume from a set of primitives.
        /// </summary>
        /// <param name="vertices">Vertex buffer</param>
        /// <param name="indices">Index buffer</param>
        public void ComputeFromPrimitives(DataBuffer<Vector3> vertices, DataBuffer<int> indices) {
            ComputeFromPrimitives(vertices.Buffer, indices.Buffer);
        }

        /// <summary>
        /// Compute this bounding volume from a set of primitives.
        /// </summary>
        /// <param name="vertices">Vertex buffer</param>
        /// <param name="indices">Index buffer</param>
        public abstract void ComputeFromPrimitives(Vector3[] vertices, short[] indices);

        /// <summary>
        /// Compute this bounding volume from a set of primitives.
        /// </summary>
        /// <param name="vertices">Vertex buffer</param>
        /// <param name="indices">Index buffer</param>
        public void ComputeFromPrimitives(DataBuffer<Vector3> vertices, DataBuffer<short> indices) {
            ComputeFromPrimitives(vertices.Buffer, indices.Buffer);
        }

        /// <summary>
        /// Tests if the volume intersects with this bounding volume.
        /// </summary>
        /// <param name="volume">Volume to test</param>
        /// <returns>True if they intersect</returns>
        public bool Intersects(BoundingVolume volume) {
            if(volume == null) {
                return false;
            }

            switch(volume.BoundingType) {
                case Bounding.BoundingType.AABB:
                    return Intersects(volume as BoundingBox);
                case Bounding.BoundingType.Sphere:
                    return Intersects(volume as BoundingSphere);
                case Bounding.BoundingType.OBB:
                    return Intersects(volume as OrientedBoundingBox);
            }

            return false;
        }

        /// <summary>
        /// Tests if the volume intersects with this bounding volume.
        /// </summary>
        /// <param name="box">AABB to test</param>
        /// <returns>True if they intersect</returns>
        public abstract bool Intersects(BoundingBox box);

        /// <summary>
        /// Tests if the volume intersects with this bounding volume.
        /// </summary>
        /// <param name="sphere">Sphere to test</param>
        /// <returns>True if they intersect</returns>
        public abstract bool Intersects(BoundingSphere sphere);

        /// <summary>
        /// Tests if the volume intersects with this bounding volume.
        /// </summary>
        /// <param name="obb">Oriented bounding box to test</param>
        /// <returns>True if they intersect</returns>
        public abstract bool Intersects(OrientedBoundingBox obb);

        /// <summary>
        /// Tests if the ray intersects with this bounding volume.
        /// </summary>
        /// <param name="ray">Ray to test</param>
        /// <returns>True if they intersect</returns>
        public abstract bool Intersects(Ray ray);

        /// <summary>
        /// Tests if the ray intersects with this bounding volume.
        /// </summary>
        /// <param name="ray">Ray to test</param>
        /// <param name="result">Result, true if they intersect</param>
        public abstract void Intersects(ref Ray ray, out bool result);

        /// <summary>
        /// Tests where on the volume the ray intersects, if it does.
        /// </summary>
        /// <param name="ray">Ray to test</param>
        /// <returns>Intersection result</returns>
        public abstract BoundingIntersectionRecord IntersectsWhere(Ray ray);

        /// <summary>
        /// Tests where on the volume the ray intersects, if it does.
        /// </summary>
        /// <param name="ray">Ray to test</param>
        /// <param name="result">The intersection result</param>
        public abstract void IntersectsWhere(ref Ray ray, out BoundingIntersectionRecord result);

        /// <summary>
        /// Tests if the plane intersects with this bounding volume, and if not
        /// which side the volume is on relative to the plane.
        /// </summary>
        /// <param name="plane">Plane to test</param>
        /// <returns>Plane intersection type</returns>
        public abstract PlaneIntersectionType Intersects(Plane plane);

        /// <summary>
        /// Tests if the plane intersects with this bounding volume, and if not
        /// which side the volume is on relative to the plane.
        /// </summary>
        /// <param name="plane">Plane to test</param>
        /// <param name="result">Result, the plane intersection type</param>
        public abstract void Intersects(ref Plane plane, out PlaneIntersectionType result);

        /// <summary>
        /// Determines if the point is contained within this bounding volume
        /// </summary>
        /// <param name="point">Point to test</param>
        /// <returns>Containment type</returns>
        public abstract ContainmentType Contains(Vector3 point);

        /// <summary>
        /// Determines if the volume is contained within this bounding volume.
        /// </summary>
        /// <param name="volume">Bounding volume to test</param>
        /// <returns>Containment type</returns>
        public ContainmentType Contains(BoundingVolume volume) {
            if(volume == null) {
                return ContainmentType.Outside;
            }

            switch(volume.BoundingType) {
                case BoundingType.AABB:
                    return Contains(volume as BoundingBox);
                case BoundingType.OBB:
                    return Contains(volume as OrientedBoundingBox);
                case BoundingType.Sphere:
                    return Contains(volume as BoundingSphere);
            }

            return ContainmentType.Outside;
        }

        /// <summary>
        /// Determines if the AABB is contained within this bounding volume.
        /// </summary>
        /// <param name="box">AABB to test</param>
        /// <returns>Containment type</returns>
        public abstract ContainmentType Contains(BoundingBox box);

        /// <summary>
        /// Determines if the sphere is contained within this bounding volume.
        /// </summary>
        /// <param name="sphere">Bounding sphere to test</param>
        /// <returns>Containment type</returns>
        public abstract ContainmentType Contains(BoundingSphere sphere);

        /// <summary>
        /// Determines if the oriented bounding box is contained within this bounding volume.
        /// </summary>
        /// <param name="obb">Oriented bounding box to test</param>
        /// <returns>Containment type</returns>
        public abstract ContainmentType Contains(OrientedBoundingBox obb);

        /// <summary>
        /// Merges this bounding volume with a second one. The resulting bounding
        /// volume is stored locally and will contain the original volumes completely.
        /// </summary>
        /// <param name="volume">Volume to merge with</param>
        public abstract void MergeLocal(BoundingVolume volume);

        /// <summary>
        /// Merges this bounding volume with a second one. The resulting
        /// bounding volume is returned as a new object and will contain the 
        /// original volumes completely. The returned value will be of the
        /// same bounding type as the caller.
        /// </summary>
        /// <param name="volume">Volume to merge with</param>
        /// <returns>New volume that contains the original two</returns>
        public abstract BoundingVolume Merge(BoundingVolume volume);

        /// <summary>
        /// Transforms this volume by the SRT transform. The transformed
        /// volume is stored locally.
        /// </summary>
        /// <param name="transform">SRT transform</param>
        public abstract void TransformLocal(Transform transform);

        /// <summary>
        /// Transforms this volume by the SRT transform and creates a 
        /// new bounding volume that contains the result. The bounding volume
        /// will be of the same type as the caller.
        /// </summary>
        /// <param name="transform">SRT transform</param>
        /// <returns>New transformed volume</returns>
        public abstract BoundingVolume Transform(Transform transform);

        /// <summary>
        /// Serializes this BoundingVolume.
        /// </summary>
        /// <param name="output">Output to write to</param>
        public virtual void Write(ISavableWriter output) {
            output.Write("Center", m_center);
        }

        /// <summary>
        /// Deserializes this BoundingVolume.
        /// </summary>
        /// <param name="input">Input to read from</param>
        public virtual void Read(ISavableReader input) {
            m_center = input.ReadVector3();
        }
    }
}
